package com.gmrz.uaf.common.service.challengestore;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.service.challengestore.IChallengeData;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.common.UAFRuntimeException;
import com.gmrz.uaf.crypto.CryptoEngine;
import com.gmrz.uaf.crypto.spi.handlers.CipherHandler;
import com.gmrz.uaf.protocol.v1.schema.WhiteList;
import com.gmrz.util.Convert;
import com.gmrz.util.Strings;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;

public class UAFChallengeStoreServiceRedisClient2
        implements ChallengeStoreService {
    private static final Logger LOG = LogManager.getLogger(UAFChallengeStoreServiceRedisClient2.class);

    private static int expireDuration = 0;
    private static int jedisClientTimeout = 0;
    private static boolean shouldEncryptChallengeData = false;
    private static final String STATUS_OK = "OK";
    private static String redisServerMaster = null;
    private static int database = 0;

    private CryptoEngine cryptoEngine = null;

    private JedisPool pool = null;
    private String redisServerMasterPwd = null;
    private JedisPoolConfig poolConfig = null;
    private CipherHandler serverDataCipherHandler = null;
    private CipherHandler passwordCipherHandler = null;
    private static final String METHOD_NAME_PREFIX = "set";
    private static final String SEPERATOR = " ";

    public void init(Properties props)
            throws ChallengeStoreException {
        try {
            LOG.debug(" Initializing the Challenge store (redis client)");
            if (props == null) {
                LOG.error(" Input properties cannot be null");
                throw new ChallengeStoreException(" Input properties cannot be null");
            }
            if (this.cryptoEngine == null) {
                LOG.debug(" Initializing the crypto engine");
                this.cryptoEngine = GuiceUtil.getProcessorInjector().getInstance(CryptoEngine.class);
                this.serverDataCipherHandler = this.cryptoEngine.getServerDataCipherHandler();
                this.passwordCipherHandler = this.cryptoEngine.getDBPasswordCipherHandler();
            }
            this.poolConfig = getJedisPoolConfigProperties(props);

            expireDuration = Integer.parseInt(props.getProperty("uaf.challenge.validity.seconds"));
            redisServerMaster = props.getProperty("uaf.challenge.store.redis.server.master.name");
            jedisClientTimeout = Integer.parseInt(props.getProperty("uaf.challenge.store.redis.client.request.timeout.seconds"));

            jedisClientTimeout *= 1000;
            this.redisServerMasterPwd = props.getProperty("uaf.challenge.store.redis.server.encrypted.password");

//            if (!Strings.isNullOrEmpty(this.redisServerMasterPwd)) {
//                this.redisServerMasterPwd = decryptMasterPassword();
//            }
            database = Integer.parseInt(props.getProperty("uaf.challenge.store.redis.server.database.number"));
            shouldEncryptChallengeData = Boolean.parseBoolean(props.getProperty("uaf.challenge.store.redis.server.should.encrypt.data"));

            String sentinelServerList = props.getProperty("uaf.challenge.store.redis.sentinel.servers");
            String[] listSentinelServers = sentinelServerList.split(SEPERATOR);

            String[] hostPort = listSentinelServers[0].split(":");

            verifyProperties(props);
            if (!Strings.isNullOrEmpty(this.redisServerMasterPwd)) {
                String passFlag = props.getProperty("uaf.challenge.store.redis.server.encrypted.password.flag");
                if(Strings.isNotBlank(passFlag) && "true".equals(passFlag.trim())){
                    String pwd = this.redisServerMasterPwd;
                    this.redisServerMasterPwd = com.gmrz.uas.security.App.getPassword(pwd);
                }
                this.pool = new JedisPool(this.poolConfig, hostPort[0], Integer.parseInt(hostPort[1]), jedisClientTimeout, this.redisServerMasterPwd, database);
            } else {
                this.pool = new JedisPool(this.poolConfig, hostPort[0], Integer.parseInt(hostPort[1]), jedisClientTimeout, null, database);
                //this.pool = new JedisPool(this.poolConfig, hostPort[0], Integer.parseInt(hostPort[1]), jedisClientTimeout);
            }
            testConnection();

            LOG.info("Initialized the jedis client pool successfully. ");

            LOG.info("Initialized the jedis client pool with following cofiguration : \n redisServerMaster : {}  \n sentinelServerList : {} \n database : {}\n timeout : {} \n shouldEncryptChallengeData : {}\n expiration : {} ", redisServerMaster, sentinelServerList, Integer.valueOf(database), Integer.valueOf(jedisClientTimeout), Boolean.valueOf(shouldEncryptChallengeData), Integer.valueOf(expireDuration));
        } catch (Exception ex) {
            LOG.error("Unable to initialize the challenge store service. ", ex);
            throw new ChallengeStoreException("Failed to init the challenge store service : " + ex);
        }
    }

    @Override
    public void initCache(Properties props) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("facet.id verify resources");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            String status = jedis.set("uaf.facet.id", props.getProperty("uaf.facet.id").toString());

            verifyReturnStatus(status);

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }

    }

    private String decryptMasterPassword() {
        try {
            if (this.passwordCipherHandler == null) {
                throw new UAFRuntimeException("Cipher handler is not initialized.");
            }
            this.redisServerMasterPwd = Convert.fromUtf8(this.passwordCipherHandler.decrypt(Convert.fromBase64(this.redisServerMasterPwd)));
            return this.redisServerMasterPwd;
        } catch (Exception ex) {
            LOG.error("Failed to decrypt the redis server master password.", ex);
            throw new UAFRuntimeException("Failed to decrypt the redis server master password.", ex);
        }
    }

    public String put(IChallengeData data)
            throws ConcurrentModificationException, ChallengeStoreException {
        Jedis jedis = null;
        try {
            if (data == null) {
                throw new ChallengeStoreException("Unable to perform put operation, input data is NULL");
            }
            if (this.cryptoEngine == null) {
                throw new ChallengeStoreException("ChallengeStore service crypto is not initialized correctly, data:" + data);
            }
            String key = data.getKey();
            byte[] serializedData = null;
            byte[] wrappedSerializedData = null;

            LOG.debug("verify resources");
            // 校验资源
            verifyResource(this.pool);
            // 从连接池中获取一个连接
            jedis = this.pool.getResource();
            verifyResource(jedis);
            // 将key字符串序列化为字节数组
            byte[] serializedKey = SerializationUtils.serialize(key);
            LOG.debug("Fetching data using key={}", key);
            // 根据key获取value
            byte[] serializedStoredData = jedis.get(serializedKey);

            if (serializedStoredData == null) {
                LOG.debug("Following key does NOT exist in the cache : {}", key);
                serializedData = SerializationUtils.serialize(data);

                if (shouldEncryptChallengeData == true) {
                    LOG.debug("Wrapping the challenge data : ");
                    // 对挑战数据进行加密
                    wrappedSerializedData = this.serverDataCipherHandler.encrypt(serializedData);
                } else {
                    LOG.debug("Do not wrap the challenge data : ");
                    // 不加密挑战数据
                    wrappedSerializedData = serializedData;
                }
                // 存储key-value，并设置过期时间
                String status = jedis.setex(serializedKey, expireDuration, wrappedSerializedData);

                verifyReturnStatus(status);
            } else {
                LOG.error("Key : {}  already exists, considering it Replay Attack", key);

                throw new ChallengeStoreException("Possible Replay Attack");
            }
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } catch (JedisException ex) {
            LOG.error("Unable to perform operation on ChallengeData from cache with ChallengeData : {}", data, ex);
            throw new ChallengeStoreException("Unable to perform operation, put ChallengeData:" + data, ex);
        } catch (Exception ex) {
            LOG.error("Unable to put ChallengeData: {}", data, ex);
            throw new ChallengeStoreException("Unable to put ChallengeData:" + data, ex);
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
        return data.getKey();
    }

    @Override
    public void putvalue(String key, HashMap<String, String> value) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("redis put value");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            String status = jedis.hmset(key, value);

            verifyReturnStatus(status);

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }

    @Override
    public List<String> getvalue(String key, String[] fields) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("redis get map value");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);
            // hmget获取hash中指定的 field 的值，redis中的key-value（hash结构）
            return jedis.hmget(key, fields);
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }

    @Override
    public void putString(String key, String value) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("redis put value");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            String status = jedis.set(key, value);

            verifyReturnStatus(status);

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }


    public IChallengeData get(String key)
            throws IllegalArgumentException, ChallengeStoreException {
        Jedis jedis = null;
        IChallengeData data = null;
        try {
            if (key == null) {
                throw new ChallengeStoreException("Unable to get ChallengeData, input key is NULL");
            }
            if (this.cryptoEngine == null) {
                throw new ChallengeStoreException("ChallengeStore service crypto is not initialized correctly, key:" + key);
            }

            LOG.debug("verify resources");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            LOG.debug("Fetching data using key = {}", key);
            byte[] serializedKey = SerializationUtils.serialize(key);

            byte[] serializedData = jedis.get(serializedKey);

            if (serializedData != null) {
                byte[] unwrappedSerializedData = null;
                if (shouldEncryptChallengeData == true) {
                    unwrappedSerializedData = this.serverDataCipherHandler.decrypt(serializedData);
                } else {
                    unwrappedSerializedData = serializedData;
                }

                data = SerializationUtils.deserialize(unwrappedSerializedData);
            }
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server: ", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } catch (JedisException ex) {
            LOG.error("Unable to perform operation on ChallengeData from cache with key : {}", key, ex);
            throw new ChallengeStoreException("Following exception occured when performing the operation  Key=" + key, ex);
        } catch (Exception ex) {
            LOG.error("Unable to get ChallengeData from cache with key : {}", key);
            throw new ChallengeStoreException("Unable to get ChallengeData from cache with Key=" + key, ex);
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
        return data;
    }

    //@Override
    public HashMap<String, String> getfactsId(String keyvalue) throws IllegalArgumentException, ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("facet.id getfactsId");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            HashMap<String, String> factsIdValue = new HashMap<String, String>();
            factsIdValue = (HashMap<String, String>) jedis.hgetAll(keyvalue);

            return factsIdValue;

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }

    @Override
    public String getvalue(String keyvalue) throws IllegalArgumentException, ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("redis getvalue :{}", keyvalue);
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            String factsIdValue = null;
            factsIdValue = jedis.get(keyvalue);

            return factsIdValue;

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }


    public String update(IChallengeData data)
            throws ConcurrentModificationException, ChallengeStoreException {
        Jedis jedis = null;
        try {
            if (data == null) {
                throw new ChallengeStoreException("Unable to update, input data is NULL");
            }
            if (this.cryptoEngine == null) {
                throw new ChallengeStoreException("ChallengeStore service crypto is not initialized correctly, data:" + data);
            }

            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            byte[] serializedData = SerializationUtils.serialize(data);
            byte[] serializedKey = SerializationUtils.serialize(data.getKey());

            String status = jedis.watch(new byte[][]{serializedKey});

            verifyReturnStatus(status);

            byte[] serializedStoredData = jedis.get(serializedKey);
            if (serializedStoredData == null) {
                LOG.debug("Inserting the following key : {}, as it does not exist : ", data.getKey());
            }

            byte[] wrappedSerializedData = null;

            if (shouldEncryptChallengeData == true) {
                wrappedSerializedData = this.serverDataCipherHandler.encrypt(serializedData);
            } else {
                wrappedSerializedData = serializedData;
            }

            LOG.debug("Beginning the transaction on the key {}", data.getKey());
            Transaction jedisTransaction = jedis.multi();

            Response response = jedisTransaction.setex(serializedKey, expireDuration, wrappedSerializedData);
            LOG.debug(" returned response : {}", response.toString());

            List transactionRet = jedisTransaction.exec();

            if ((transactionRet != null) && (transactionRet.size() == 1) && (transactionRet.get(0).equals(STATUS_OK))) {
                LOG.debug(" successfully updated the following key : {} ", data.getKey());
            } else {
                LOG.debug(" Unable to set the key-value pair in jedis transaction, key : {}", data.getKey());
                throw new ChallengeStoreException("Unable to set the key : " + data.getKey());
            }
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } catch (JedisException ex) {
            LOG.error("Unable to perform operation on ChallengeData from cache with ChallengeData : {}", data);
            throw new ChallengeStoreException("Unable to perform operation, put ChallengeData:" + data, ex);
        } catch (Exception ex) {
            LOG.error("Unable to update ChallengeData: {} ", data, ex);
            throw new ChallengeStoreException("Unable to update ChallengeData:" + data, ex);
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
        return data.getKey();
    }

    public void delete(String key)
            throws ConcurrentModificationException, ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug(" Deleting the following key : {} ", key);
            if ((key == null) || (key.isEmpty())) {
                throw new ChallengeStoreException("Unable to delete, input key is NULL or empty");
            }
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            byte[] serializedKey = SerializationUtils.serialize(key);

            Long returnVal = null;

            returnVal = jedis.del(serializedKey);
            if ((returnVal != null) && (returnVal.longValue() != 0L)) {
                LOG.debug(" successfully deleted the key : {}, returnVal : {}", key, returnVal);
            } else
                LOG.debug("Unable to delete the key; the key does not exist : {}, returnVal : {}", key, returnVal);
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server: ", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } catch (JedisException ex) {
            LOG.error("Unable to perform operation on ChallengeData from cache with key : {}", key, ex);
            throw new ChallengeStoreException("Following exception occured when performing the operation  Key=" + key, ex);
        } finally {
            if (jedis != null)
                this.pool.returnResource(jedis);
        }
    }

    public synchronized void shutdown()
            throws ChallengeStoreException {
        if (this.pool != null) {
            LOG.debug(" Clearing off the Connection Pool resource");
            this.pool.destroy();
            this.pool = null;
        }
        LOG.info(" Shutdown complete for Jedis Connection Pool resources.");
    }

    private void testConnection()
            throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug(" Testing connection to the redis database ");

            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            String result = jedis.ping();
            LOG.debug("Return result of test connection with Redis server using PING : {}", result);
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server: {}", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } catch (JedisException ex) {
            LOG.error("Unable to  perform operation: ", ex);
            throw new ChallengeStoreException("Unable to perform operation");
        } finally {
            if (jedis != null)
                this.pool.returnResource(jedis);
        }
    }

    private static <T> void verifyResource(T resource)
            throws ChallengeStoreException {
        if (resource == null) {
            LOG.error("Unable to allocate a connection pool resource");
            throw new ChallengeStoreException("Unable to allocate a connection pool resource");
        }
    }

    private static void verifyReturnStatus(String status) throws ChallengeStoreException {
        if ((status != null) && (status.equalsIgnoreCase(STATUS_OK))) {
            LOG.debug("Success in verifying the return response.");
        } else {
            LOG.error("Failure returned with status : {}", status);
            throw new ChallengeStoreException("Failure returned for the operation, status : " + status);
        }
    }

    private void verifyProperties(Properties props)
            throws ChallengeStoreException {
        if (props != null) {
            String serverList = props.getProperty("uaf.challenge.store.redis.sentinel.servers");

            if ((serverList == null) || (serverList.isEmpty())) {
                throw new ChallengeStoreException("Challenge storage is provided as redis but sentinels configuration is missing.");
            }

        }

        if (this.poolConfig == null) {
            throw new ChallengeStoreException("Unable to create pool config.");
        }
        if ((redisServerMaster == null) || (redisServerMaster.isEmpty()))
            throw new ChallengeStoreException("Redis server master cannot be null or empty.");
    }

    private JedisPoolConfig getJedisPoolConfigProperties(Properties props)
            throws ChallengeStoreException {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        if (props != null) {
            Enumeration keys = props.keys();
            while (keys.hasMoreElements()) {
                String key = (String) keys.nextElement();
                if (key.startsWith("uaf.challenge.store.jedis.pool.config")) {
                    String value = props.getProperty(key);
                    LOG.debug(" Found the jedis pool config, property : {}, and value: {}", key, value);
                    setPropertiesDynamic(poolConfig, key, value);
                }
            }
        }
        return poolConfig;
    }

    private void setPropertiesDynamic(Object o, String key, String value)
            throws ChallengeStoreException {
        if ((value != null) && (!value.isEmpty()))
            try {
                key = key.substring(key.lastIndexOf(46) + 1, key.length());

                char[] ca = key.toCharArray();
                ca[0] = Character.toUpperCase(ca[0]);
                key = new String(ca);

                String methodName = METHOD_NAME_PREFIX + key;
                Method m = findMethodInClass(o.getClass(), methodName);
                if (m != null) {
                    Class[] types = m.getParameterTypes();
                    Object param = null;

                    if (types[0].getSimpleName().equals("int")) {
                        param = new Integer(value);
                    } else if (types[0].getSimpleName().equals("long")) {
                        param = new Long(value);
                    } else if (types[0].getSimpleName().equals("boolean")) {
                        param = new Boolean(value);
                    } else if (types[0].getSimpleName().equals("String")) {
                        param = new String(value);
                    }
                    m.invoke(o, param);
                }
            } catch (Exception ex) {
                LOG.error("Error setting Jedis Client properties : {} ", ex.getMessage());
                throw new ChallengeStoreException("Error setting Jedis Client properties", ex);
            }
    }

    private Method findMethodInClass(Class<?> clazz, String methodName) {
        Method m = null;
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            String name = method.getName();
            if (methodName.endsWith(name)) {
                LOG.info("Found method with name= {}", name);
                m = method;
                return m;
            }
        }
        LOG.info("Method with name: {} , not found in class {} ", methodName, clazz.getName());
        return m;
    }

    @Override
    public List<WhiteList> getWhiteList(String key) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("get white list from redis,key:{}", key);
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);
            String value = jedis.get(Constants.WHITE_LIST_KEY);

            if (value != null && !value.isEmpty()) {
                Gson gson = new Gson();
                //Gson gson = UAFSchemaBuilder.getGson();

                Type type = new TypeToken<List<WhiteList>>() {
                }.getType();
                List<WhiteList> list = gson.fromJson(value, type);
                return list;
            }
            return null;

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } catch (Exception e) {
            LOG.error("Unable to fetch white list from  redis server:", e);
            throw new ChallengeStoreException("Unable to fetch white list from  redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }

    @Override
    public void setWhiteList(String key, List<WhiteList> list) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("redis put value");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            // Gson gson = UAFSchemaBuilder.getGson();
            Gson gson = new Gson();
            String json = gson.toJson(list);
            String status = jedis.set(Constants.WHITE_LIST_KEY, json);
            verifyReturnStatus(status);

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }

    @Override
    public String getRedisValue(String key) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            String value = jedis.get(key);
            LOG.debug("get data from redis key:{},value:{}", key, value);
            return value;

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }

    @Override
    public void setRedisValue(String key, int seconds, String value) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("redis put value");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            String status = jedis.setex(key, seconds, value);

            verifyReturnStatus(status);

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }

    @Override
    public void setrangeRedisValue(String key, String value) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("redis put value");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);
            jedis.setrange(key, 0, value);
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }

    @Override
    public void deleteRedisValue(String key) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("redis delete value");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            Long returnVal = jedis.del(key);
            if ((returnVal != null) && (returnVal.longValue() != 0L)) {
                LOG.debug(" successfully deleted the key : {}, returnVal : {}", key, returnVal);
            } else
                LOG.debug("Unable to delete the key; the key does not exist : {}, returnVal : {}",
                        key, returnVal);
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }

    @Override
    public void sadd(String key, String... members) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            LOG.debug("redis put value");
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            jedis.sadd(key, members);

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            if (jedis != null) {
                this.pool.returnBrokenResource(jedis);
                jedis = null;
            }
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                this.pool.returnResource(jedis);
            }
        }
    }

    @Override
    public Set<String> smembers(String key) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);

            Set<String> value = jedis.smembers(key);

            return value;

        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     *
     * @param lockKey    锁
     * @param requestId  请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    @Override
    public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);
            String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return false;

    }

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     *
     * @param lockKey   锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    @Override
    public synchronized boolean releaseDistributedLock(String lockKey, String requestId) throws ChallengeStoreException {

        Jedis jedis = null;
        try {
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);
            // Lua脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            // 使用 eval 执行Lua脚本脚本
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return false;

    }

    @Override
    public void updateCertNumCache() throws ChallengeStoreException {
        Jedis jedis = null;
        try {
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);
            jedis.incr(Constants.CERT_NUM);
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    @Override
    public long incr(String key) throws ChallengeStoreException{
        Jedis jedis = null;
        try {
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);
            return jedis.incr(key);
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
    @Override
    public long decr(String key) throws ChallengeStoreException{
        Jedis jedis = null;
        try {
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);
            return jedis.decr(key);
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
    @Override
    public void deleteAll(String key) throws ChallengeStoreException{
        Jedis jedis = null;
        try {
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);
            Set<String> keys = jedis.keys(key);
            if(null != keys && keys.size() > 0){
                String[] keysStr = new String[keys.size()];
                keys.toArray(keysStr);
                jedis.del(keysStr);
            }
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    @Override
    public long expire(String key,int seconds) throws ChallengeStoreException{
        Jedis jedis = null;
        try {
            verifyResource(this.pool);
            jedis = this.pool.getResource();
            verifyResource(jedis);
            return jedis.expire(key,seconds);
        } catch (JedisConnectionException jce) {
            LOG.error("Unable to connect to redis server:", jce);
            throw new ChallengeStoreException("Unable to connect to Redis server");
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
}
